package com.batchengine;

import com.batchengine.service.BatchEngineHttpHandler;
import com.batchengine.service.Service;
import com.batchengine.startup.BatchhEngineCache;
import com.batchengine.startup.ServiceClient;
import com.batchengine.util.Listener;
import static com.batchengine.util.LoggingFactory.createLogger;
import com.batchengine.util.SocketListener;
import com.sun.net.httpserver.HttpServer;
import org.apache.log4j.Logger;

import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.UnknownHostException;
import java.sql.SQLException;
import java.util.Collection;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors;

/**
 * Created by sunil.incubator@yahoo.com.
 * User: sunil.kalva
 * Date: Jul 12, 2009
 * <p/>
 * This class implements a generic server. The server can support a number of services. Each service is associated
 * with a port that the server is listening to. When a connection is accepted on the port, the server invokes the
 * service to handle the connection.
 */
public abstract class Server {

    private static final Logger logger = createLogger();

    /**
     * Maps port numbers to their listener threads.
     */
    protected Map<String, SocketListener> listeners;

    /**
     * Creates a <tt>Server</tt> object.
     */
    public Server() {
        listeners = new ConcurrentHashMap<String, SocketListener>();
    }

    /**
     * Starts a service that does not require authentication.
     *
     * @param service the service to start
     * @param port    the port of the service
     * @throws java.io.IOException a
     */
    protected synchronized void startService(Service service, int port) throws IOException {
        startService(service, port, null);
    }

    /**
     * Starts a service that requires authentication.
     *
     * @param service  the service to start
     * @param port     the port of the service
     * @param password the password needed for authentication
     * @throws java.io.IOException a
     */
    protected synchronized void startService(Service service, int port, String password) throws IOException {
        startService(service, port, password, true);
    }

    /*
        This method is used for to start the user defined modules
     */
    protected void startup() throws SQLException, UnknownHostException {

        logger.debug("@@@@@@@@@@@@@@ starting the server " + InetAddress.getLocalHost().getHostAddress() + " @@@@@@@@@@@@@@");
        BatchhEngineCache.loadCache();
    }

    protected static void stopService(String host, int port, String password) {

        ServiceClient client;
        try {

            client = new ServiceClient(host, port);
            client.match("Password: ", password, true);
            client.match("> ", "stop server", true);
            client.match("> ", "close", true);
            client.close();
        } catch (Exception ignored) {
        }
    }

    protected static void kill(String host, int port, String password) {

        try {

            ServiceClient client = new ServiceClient(host, port);
            client.match("Password: ", password, true);
            client.match("> ", "kill", true);
            client.close();
        } catch (Exception ignored) {
        }
    }

    protected static void interact(String host, int port) {

        try {
            ServiceClient client = new ServiceClient(host, port);
            client.interact();
        } catch (Exception ignored) {
        }
    }

    public void shutdown() {

        logger.debug("Cleaning Up Server Service");
        if (listeners != null && !listeners.isEmpty()) {
            Collection<SocketListener> values = listeners.values();
            Listener listner = null;
            for (SocketListener value : values) {
                if (value instanceof Listener) {
                    listner = value;
                    if (listner != null) {
                        try {
                            logger.debug("Stoping " + listner.getClass().getName());
                            listner.pleaseStop();
                        } catch (Exception e) {
                            //TODO : should this be logged ?
                            e.printStackTrace();
                        }
                    }
                }
            }
            listeners.clear();
        }
    }

    /**
     * Starts a service that requires authentication.
     *
     * @param service             the service to start
     * @param port                the port of the service
     * @param password            the password needed for authentication
     * @param startAsDaemonThread the enables the threads to be started the Daemon threads or not
     *                            This is very important, if the thread is not a daemon thread and
     *                            if it is blocked on a IO call then the application will not exit
     *                            untils the IO operation finishes and the loop terminates.
     * @throws java.io.IOException a
     */
    protected synchronized void startService(Service service, int port, String password, boolean startAsDaemonThread)
            throws IOException {

        try {

            service.setServerName("-BatchServer");

            SocketListener socketListener = new SocketListener(service, port, password);
            listeners.put(Integer.toString(port), socketListener);
            socketListener.setDaemon(startAsDaemonThread);
            socketListener.setName("Server-socketListener");
            socketListener.start();


            InetSocketAddress addr = new InetSocketAddress(11112);
            HttpServer server = HttpServer.create(addr, 0);
            server.createContext("/", new BatchEngineHttpHandler());
            server.setExecutor(Executors.newCachedThreadPool());
            server.start();
            logger.debug("Server started listening on port 11112");
        } catch (IOException e) {
            // This exception occurs if the server socket cannot be created. Usually it means that the port is
            // already in use.
            logger.debug("The server socket could not be created. Invalid port or port already in use.");
            logger.debug("The exception message is <" + e.getMessage() + ">.");
            throw e;
        }
    }
}
